#define _GNU_SOURCE

#include<stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

#include <omp.h>

#include <dfruntime.h>

#include "misc.h"

#include "global_queue.h"
#include "asyncpages.h"
#include "scheduling.h"
#include "config.h"
#include "memory_management.h"
#include "owm_management.h"
#include "wish_queue.h"

#ifdef USE_NATIVE
#include "native_shm.h"
#else
#error "TFSIM support needs to be implemented here."
#endif



// Some missing definitions :
extern void GOMP_single_start();
extern void GOMP_barrier();
extern void GOMP_parallel_start();
extern void GOMP_parallel_end();

/* The current frame pointer is stored here in TLS */
hidden_attr
__thread struct frame_struct * current_fp;


/* A linked list of created frames (to free at tEnd )*/
// TODO : fusion this with async page management ?
struct created_frame {
    struct created_frame * next;
    struct frame_struct *fp;
};

static __thread struct created_frame * created_frames = NULL;

static void add_created_frame( struct frame_struct *fp ) {
    struct created_frame * newnode = calloc( 1, sizeof(struct created_frame));
    newnode->fp = fp;
    newnode->next = created_frames;
    created_frames = newnode;
}

void release_created_frames() {
    while ( created_frames != NULL ) {
        _DEBUG( "Release created frame : for %p", created_frames->fp);
        safe_release_page( created_frames->fp );
        perform_tdecs(created_frames->fp);
        // Advertise release

        struct created_frame * tmp = created_frames;
        created_frames = created_frames->next;
        free( tmp );
    }
}


/* Get the frame pointer of the current thread */
struct frame_struct * dfrt_get_cfp() {
  return current_fp;  
}


/* Create a new thread, with frame pointer size, and sync counter */
struct frame_struct * dfrt_tcreate( int sc, int fp_size) {
  /* No validity checks... */

  /* Allocates the frame in global memory. */
  struct frame_struct * frame_pointer = dfrt_mem_malloc( fp_size );
  if ( frame_pointer == NULL ) {
      FATAL( "Insufficient memory : coundln't allocate frame.");
  }
  _DEBUG("TCREATED : %p", frame_pointer);

  add_created_frame( frame_pointer );
  async_request_page( frame_pointer, fp_size, true );
  frame_pointer->sc = sc;

  return frame_pointer;
}


/* Decrease the synchronization counter by one */
void dfrt_effective_tdecrease( struct frame_struct * data ) {
    DEBUG( "Effective decrease on %p", data);
  struct frame_struct *fp = data;
  

  // TODO think about it.
  int newval = __sync_sub_and_fetch (&(fp->sc), 1);
  

  if ( newval  == 0 ) {
    /* Then, we should schedule the task */
    schedule_regulate( fp );       
  }
}

/*void dfrt_delayeduntil_tdecrease( struct frame_struct *to_decrease, void * to_wait ) {
    // Add to wishlist :
    add_to

}*/


/* Destroy the current thread */
void dfrt_tend() {
    schedule_has_proper_termination();
    release_created_frames();
}



/* **** Initialization code **** */

static void slaves_wait( __attribute__((unused)) void * _dummy) {
        native_shm_init_last();
        GOMP_single_start();
        GOMP_barrier();
}

static int _num_nodes;
static int _num_threads;

static int _node_id;

#include "native_shm.h"

static pid_t *pidvec;


struct misc_info mainentry_misc = {"MainEntry", "Progam EP"};
struct wrdesc_ep {
    int count;
} __packed ;



void dfrt_init( int nodenum, int threadnum, void *real_main ) {
    _num_nodes = nodenum;
    _num_threads = threadnum;

    // Pre-fork initializations.
    pidvec = calloc( nodenum, sizeof(pid_t));
    native_shm_init_pre_fork();
    gqueue_init_pre_fork();
    // Fork :
    for ( int i = 0; i < nodenum-1; ++ i ) {
        pidvec[i] = fork();

        if ( pidvec[i] == 0 ) {
            _node_id = i+1;
            break;
        }

    }

    omp_set_num_threads(threadnum);

#pragma omp parallel
{
        // Post thread creation, TLS initializations.
        native_shm_init_last();
        dfrt_owm_tls_init();
        scheduling_init_tls();
#pragma omp single
{
        async_page_management_init();
        gqueue_init_last();
        dfrt_mem_allthread_barrier();

        if ( dfrt_get_node_num() == 0 ) {

            struct dfrt_info_ep {
                DFRT_INFO_CONTENT;
            } __packed;


            struct dfrt_info_ep mainentry_infos; // TODO

            mainentry_infos.fn = (void(*)()) real_main;

            struct frame_struct * mainentry_frame =
                    dfrt_tcreate(1, sizeof(struct frame_struct));
            mainentry_frame->infos =
                    (struct dfrt_info *) &mainentry_infos;
            // We need to release it because tend won't do it for us
            // ( tcreate => acquire is made )
            release_created_frames();
            dfrt_effective_tdecrease(mainentry_frame);

        } else {
            gqueue_steal_tasks(20);
        }
}
#pragma omp single
        {
        DEBUG("%d Passed exit barrier : this shouldn't happen.", dfrt_get_node_num());
        gqueue_set_termination_bit(true);
        }
}


    // Wait for processes end.
    for ( int i = 0; i < _num_nodes-1; ++i ) {
        if ( !pidvec[i] )
            break;
        waitpid(pidvec[i], NULL, 0 );
    }
}

int dfrt_whoami() {
    return _node_id * _num_threads + omp_get_thread_num();
}

int dfrt_get_thread_num() {
    return omp_get_thread_num();
}



int dfrt_get_node_num() {
    return _node_id;
}

int dfrt_get_num_nodes() {
    return _num_nodes;
}

int dfrt_get_num_threads() {
    return _num_threads;
}

#ifdef DFRT_ENABLE_CTORDTOR
/* Add a main, omp aware ctor and dtor */



__attribute__((constructor))
void pre_main() {
	GOMP_parallel_start(slaves_wait, NULL,0);
	GOMP_single_start();
}

__attribute__((destructor)) 
void post_main() {
	GOMP_barrier();
	GOMP_parallel_end();
}

#endif
